package org.springblade.modules.mes.opcua.server;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.ObjectUtil;
import org.springblade.modules.mes.base.constant.MatOtherTypeEnum;
import org.springblade.modules.mes.base.constant.ScanTypeEnum;
import org.springblade.modules.mes.base.entity.*;
import org.springblade.modules.mes.base.service.IStationAssemblyService;
import org.springblade.modules.mes.base.service.IStationBatchService;
import org.springblade.modules.mes.opcua.constant.CountMapUtil;
import org.springblade.modules.mes.opcua.constant.OpcUaCache;
import org.springblade.modules.mes.opcua.vo.OpcUaVO;
import org.springblade.modules.mes.quality.entity.AnalysisMaterialCompositionMysql;
import org.springblade.modules.mes.quality.entity.AnalysisProcessParam;
import org.springblade.modules.mes.quality.service.IAnalysisMaterialCompositionMysqlService;
import org.springblade.modules.mes.quality.service.IAnalysisProcessParamService;
import org.springblade.modules.mes.quality.vo.MaterialCountVO;
import org.springframework.stereotype.Service;

import java.util.*;

import static org.springblade.modules.mes.opcua.constant.OpcUaError.*;

/**
 * 产品生产相关
 */
@Service
@AllArgsConstructor
@Slf4j
public class ScanService {

	private final IStationAssemblyService stationAssemblyService;

	private final IStationBatchService stationBatchService;

	private final IAnalysisMaterialCompositionMysqlService analysisMaterialCompositionService;

	private final IAnalysisProcessParamService analysisProcessParamService;

	private final ToolService toolService;

	private final RequestService requestService;

	/**
	 * 申请总成流水号
	 */
	@DSTransactional
	@DS("master")
	public void barcodeCheck(OpcUaVO ctx) throws Exception {
		try {
			//查询站位
			ProduceSite site = OpcUaCache.getBySiteCode(ctx.getSiteCode());
			if (site == null) {
				toolService.plcMessage(ctx, E01000);
				toolService.plcScanStatus(ctx, (short) 3);
				return;
			}

			//读取扫码值
			Object scanCodeObject = toolService.plcScanCode(ctx);
			String scanCode = scanCodeObject == null ? "" : scanCodeObject.toString();
			log.info("读取条码数据: {}", scanCode);
			if (scanCode.length() == 0) {
				toolService.plcMessage(ctx, E31001);
				toolService.plcScanStatus(ctx, (short) 3);
				return;
			}
			//查询订单,校验条码是否为总成流水号
			LMSDetail order = toolService.findOrder(scanCode);
			if (order != null) {
				if (!"1".equals(order.getStatus())) {
					this.assemblyBarcodeExecute(order, scanCode, ctx, site);
				} else {
					toolService.plcMessage(ctx, E31000);
					toolService.plcScanStatus(ctx, (short) 3);
					return;
				}
			} else {
				this.materialProcess(scanCode, ctx, site);
			}
		} catch (Exception ex) {
			log.error("系统异常", ex);
			toolService.plcMessage(ctx, E90000);
			toolService.plcScanStatus(ctx, (short) 3);
		}

	}

	/**
	 * 扫描更改流水号逻辑
	 */
	public void materialProcess(String scanCode, OpcUaVO ctx, ProduceSite site) throws Exception {
		//物料码解析
		MaterialInfo materialInfo = toolService.findMaterialList(scanCode);
		if (ObjectUtil.isEmpty(materialInfo)) {
			toolService.plcMessage(ctx, E01003);
			toolService.plcScanStatus(ctx, (short) 3);
			return;
		}
		//替换码场景，没有订单
		if (MatOtherTypeEnum.REPLACE_CODE.name().equals(materialInfo.getOtherType())) {
			//申请订单
			if (!requestService.assemblyBarcodeApply(ctx, scanCode)) {
				toolService.plcMessage(ctx, E31010);
				toolService.plcScanStatus(ctx, (short) 3);
				return;
			}
			this.materialBarcodeExecute(scanCode, materialInfo, ctx, site);
		} else {
			this.materialBarcodeExecute(scanCode, materialInfo, ctx, site);
		}
	}

	/**
	 * 总成流水号执行方法
	 *
	 * @param order 订单
	 */
	public void assemblyBarcodeExecute(LMSDetail order, String scanCode, OpcUaVO ctx, ProduceSite site) throws Exception {
		//上架
		List<StationAssembly> stationAssemblys = stationAssemblyService.list(new QueryWrapper<StationAssembly>().lambda()
			.eq(StationAssembly::getStationCode, site.getStationCode())
			.eq(StationAssembly::getSiteCode, site.getSiteCode()));
		//找到当前订单对应上架信息
		StationAssembly stationAssembly = null;
		List<StationAssembly> otherAssemblyList = new ArrayList();
		for (StationAssembly s : stationAssemblys) {
			if (s.getAssemblyBarcode().equals(order.getBarCode())) {
				stationAssembly = s;
			} else {
				otherAssemblyList.add(s);
			}
		}
		//已经上过架
		if (!ObjectUtil.isEmpty(stationAssembly)) {
			if (1 == stationAssembly.getStatus()) {
				toolService.plcMessage(ctx, E31002);
				toolService.plcScanStatus(ctx, (short) 3);
			} else {

				//重新扫描，重新初始化，批次物料不能重复扣减
				analysisMaterialCompositionService.remove(new QueryWrapper<AnalysisMaterialCompositionMysql>().lambda()
					.eq(AnalysisMaterialCompositionMysql::getAssemblyBarcode, order.getBarCode())
					.ne(AnalysisMaterialCompositionMysql::getPartType, ScanTypeEnum.BATCH_SCAN.getIdx())
					//物料码和扫描吗相同，则这个是替换总成码的场景，不能删除
					.ne(AnalysisMaterialCompositionMysql::getChaserCode, scanCode)
					.eq(AnalysisMaterialCompositionMysql::getStation, site.getStationCode()));
				analysisProcessParamService.remove(new QueryWrapper<AnalysisProcessParam>().lambda()
					.eq(AnalysisProcessParam::getAssemblyBarcode, order.getBarCode())
					.eq(AnalysisProcessParam::getStation, site.getStationCode()));

				//扫描产品上架，赋值PLC
				toolService.plcAssemblyOnScan(ctx, stationAssembly.getPosition(), order);
				//校验当前工位是否扫描完成
				toolService.scanComplete(stationAssembly, order, ctx);

			}
			return;
		}
		//当前订单用到的物料
		PrdInfo mainPrd = OpcUaCache.getByProdCode(order.getMatCode());
		//没有上架，就验证这个码和已经上架的吗是否在同一组下，不同组，不可以一起；
		PrdInfo otherPrd;
		for (StationAssembly otherAssembly : otherAssemblyList) {
			otherPrd = OpcUaCache.getByProdCode(otherAssembly.getAssemblyNo());
			if (otherPrd != null && !otherPrd.getMaterialGroupNo().equals(mainPrd.getMaterialGroupNo())) {
				toolService.plcMessage(ctx, E31003);
				toolService.plcScanStatus(ctx, (short) 3);
				return;
			}
		}

		//验证前工序是否完成
		if (!toolService.validPreStation(order.getMatCode(), order.getMatCode(), site)) {
			toolService.plcMessage(ctx, E31004);
			toolService.plcScanStatus(ctx, (short) 3);
			return;
		}
		//主bom
		PrdBom mainBom = OpcUaCache.getBomByMatCode(order.getMatCode());
		//工位与总成流水号绑定
		stationAssembly = new StationAssembly();
		stationAssembly.setCreateTime(new Date());
		stationAssembly.setStationCode(site.getStationCode());
		stationAssembly.setSiteCode(site.getSiteCode());
		stationAssembly.setAssemblyBarcode(order.getBarCode());
		stationAssembly.setAssemblyNo(order.getMatCode());
		stationAssembly.setStatus(0);
		stationAssembly.setPosition(mainBom.getPosition());
		if (!stationAssemblyService.save(stationAssembly)) {
			toolService.plcMessage(ctx, E01005);
			toolService.plcScanStatus(ctx, (short) 3);
			return;
		}

		//扫描产品上架，赋值PLC
		toolService.plcAssemblyOnScan(ctx, stationAssembly.getPosition(), order);
		//校验当前工位是否扫描完成
		toolService.scanComplete(stationAssembly, order, ctx);

	}

	/**
	 * 物料码执行方法
	 *
	 * @param scanCode 物料码
	 */
	public void materialBarcodeExecute(String scanCode, MaterialInfo materialInfo, OpcUaVO ctx, ProduceSite site) throws Exception {
		//获取当前工位总成流水号
		List<StationAssembly> stationAssemblys = stationAssemblyService.list(new QueryWrapper<StationAssembly>().lambda()
			.eq(StationAssembly::getStationCode, site.getStationCode())
			.eq(StationAssembly::getSiteCode, site.getSiteCode())
			.eq(StationAssembly::getStatus, 0)
			.orderByAsc(StationAssembly::getCreateTime));
		if (ObjectUtil.isEmpty(stationAssemblys)) {
			toolService.plcMessage(ctx, E31006);
			toolService.plcScanStatus(ctx, (short) 3);
			return;
		}

		//批扫，一定是为了添加数量
		if (ScanTypeEnum.BATCH_SCAN.eq(materialInfo.getScanType())) {
			//更新数量
			updateBatchMatNum(scanCode, site, materialInfo);
			//修改状态
			toolService.plcScanStatus(ctx, (short) 2);
			//每个订单都需要去看看可不可以扣减
			LMSDetail order;
			for (StationAssembly stationAssembly : stationAssemblys) {
				//订单
				order = toolService.findOrder(stationAssembly.getAssemblyBarcode());
				toolService.scanComplete(stationAssembly, order, ctx);
			}
			return;
		}

		//精追物料的使用情况
		List<MaterialCountVO> traceList;
		if (ScanTypeEnum.ONE_SCAN_CHECK.eq(materialInfo.getScanType())) {
			traceList = analysisMaterialCompositionService.matCountByScanCode(scanCode);
			if (traceList == null) {
				traceList = Collections.EMPTY_LIST;
			}
		} else {
			traceList = Collections.EMPTY_LIST;
		}

		//是否包含该物料
		boolean containerMat = false;
		//全部扫描够了
		boolean scanEnough = true;

		for (StationAssembly stationAssembly : stationAssemblys) {
			//物料信息
			PrdBom mat = toolService.findBomMatByStation(stationAssembly.getAssemblyNo(), materialInfo.getMaterialNo(), stationAssembly.getStationCode());
			if (mat == null) {
				//不包含该物料
				continue;
			}
			//订单
			LMSDetail order = toolService.findOrder(stationAssembly.getAssemblyBarcode());
			if (order == null) {
				//订单不存
				continue;
			}
			//这个物料不是错的，被bom包含过
			containerMat = true;
			//查询这个订单所物料
			List<MaterialCountVO> countList = analysisMaterialCompositionService.matCountByBarCode(stationAssembly.getAssemblyBarcode(), site.getStationCode(), site.getSiteCode());
			Map<String, Long> countMap = CountMapUtil.toMap(countList);
			//物料的使用量
			Long useCount = CountMapUtil.getValue(countMap, materialInfo.getMaterialNo());
			//检查是否扫码足量了，不足则继续扫码
			if (Long.valueOf(mat.getQuota()) > useCount) {
				//有一个没有扫描够
				scanEnough = false;
				//需要单扫防止重复
				if (ScanTypeEnum.ONE_SCAN_CHECK.eq(materialInfo.getScanType())) {
					//查询这个型号的设备是否被使用过
					for (MaterialCountVO t : traceList) {
						if (!t.getBarCode().equals(stationAssembly.getAssemblyBarcode())) {
							toolService.plcMessage(ctx, E31009);
							toolService.plcScanStatus(ctx, (short) 3);
							return;
						}
					}
				}
				//记录使用
				AnalysisMaterialCompositionMysql analysisMaterialComposition = new AnalysisMaterialCompositionMysql();
				analysisMaterialComposition.setAssemblyBarcode(stationAssembly.getAssemblyBarcode());
				analysisMaterialComposition.setAssemblyNo(stationAssembly.getAssemblyNo());
				analysisMaterialComposition.setStation(site.getStationCode());
				analysisMaterialComposition.setSite(site.getSiteCode());
				analysisMaterialComposition.setLoadTime(new Date());
				analysisMaterialComposition.setPartNo(materialInfo.getMaterialNo());
				analysisMaterialComposition.setPartName(materialInfo.getMaterialName());
				analysisMaterialComposition.setScanCode(scanCode);
				//追溯精度:1:批量,2:单件
				if ("2".equals(materialInfo.getTracAccuracy())) {
					//精追码
					analysisMaterialComposition.setChaserCode(scanCode);
				} else {
					//批追码
					analysisMaterialComposition.setBatchTraceCode(scanCode);
				}
				analysisMaterialComposition.setSupplierBatch(order.getBatch());
				analysisMaterialComposition.setPartType(materialInfo.getScanType());
				analysisMaterialComposition.setQuantity(1);
				analysisMaterialCompositionService.save(analysisMaterialComposition);

				//扫描完成
				toolService.plcScanStatus(ctx, (short) 2);
				//是否扫码完成
				toolService.scanComplete(stationAssembly, order, ctx);
				break;
			}
		}

		if (!containerMat) {
			toolService.plcMessage(ctx, E31008);
			toolService.plcScanStatus(ctx, (short) 3);
			return;
		}

		if (scanEnough) {
			toolService.plcMessage(ctx, E31007);
			toolService.plcScanStatus(ctx, (short) 3);
			return;
		}
	}


	/**
	 * 更新批扫物料数量，直接覆盖现有的不需要累加
	 * 泰州做法
	 *
	 * @param scanCode
	 * @param site
	 * @param materialInfo
	 */
	private void updateBatchMatNum(String scanCode, ProduceSite site, MaterialInfo materialInfo) {
		//更新数据
		LambdaQueryWrapper<StationBatch> lambdaQueryWrapper = new QueryWrapper<StationBatch>().lambda()
			.eq(StationBatch::getSiteCode, site.getSiteCode())
			.eq(StationBatch::getStationCode, site.getStationCode())
			.eq(StationBatch::getMaterialNo, materialInfo.getMaterialNo());
		StationBatch stationBatchOld = stationBatchService.getOne(lambdaQueryWrapper);
		if (ObjectUtil.isNotEmpty(stationBatchOld)) {
			UpdateWrapper<StationBatch> wrapper = new UpdateWrapper<>();
			wrapper.eq("id", stationBatchOld.getId());
			wrapper.set("batch_barcode", scanCode);
			wrapper.set("num", materialInfo.getBoxCounts().longValue());
			wrapper.set("update_time", new Date());
			stationBatchService.update(wrapper);

		} else {
			//批追 物料替换 还是物料追加  确定了 替换
			StationBatch stationBatch = new StationBatch();
			stationBatch.setBatchBarcode(scanCode);
			stationBatch.setMaterialNo(materialInfo.getMaterialNo());
			stationBatch.setNum(materialInfo.getBoxCounts().longValue());
			stationBatch.setMaterialName(materialInfo.getMaterialName());
			stationBatch.setSiteCode(site.getSiteCode());
			stationBatch.setStationCode(site.getStationCode());
			stationBatch.setCreateTime(new Date());
			stationBatch.setUpdateTime(new Date());
			stationBatchService.save(stationBatch);
		}
	}


}
